<template>
  <svg
    ref="svgEl"
    data-configuration="1"
    class="vc-bg-white vc-shadow"
    xmlns="http://www.w3.org/2000/svg"
    version="1.1"
    :height="svgHeight * previewScale"
    :width="svgWidth * previewScale"
    :viewBox="viewPortWidth === 0 || viewPortHeight === 0 ? undefined : `0 0 ${viewPortWidth} ${viewPortHeight}`"
    :style="{
      position,
      left: 0,
      top: 0,
      transform: `translate3d(${left}px, ${top}px, 0) scale(1, 1)`,
      transformOrigin: '0 0',
    }"
  >
    <GridLine v-if="isEditor" ref="gridLineIns" :width="svgWidth" :height="svgHeight" :shares="GridShares" />
    <PipePath
      v-for="(el, i) in pipePathData"
      :id="el.id"
      :key="el.id"
      :ref="
        (ref) => {
          compInstance[el.id] = ref;
        }
      "
      :index="i"
      :center-x="el.centerX"
      :center-y="el.centerY"
      :colors="(el.colors as string[])"
      :scale-factor="scaleFactor"
      :type="(el.pipePathType as PipePathType)"
    />
    <Device
      v-for="(el, i) in deviceImageData"
      :id="el.id"
      :key="el.id"
      :ref="
        (ref) => {
          compInstance[el.id] = ref;
        }
      "
      :img-src="el.imgSrc"
      :img-name="el.imgName"
      :init-width="el.initWidth || 0"
      :index="i"
      :center-x="el.centerX"
      :center-y="el.centerY"
      :scale-factor="scaleFactor"
      @select="selectItem"
    />
    <Device
      v-for="(el, i) in deviceSvgData"
      :id="el.id"
      :key="el.id"
      :ref="
        (ref) => {
          compInstance[el.id] = ref;
        }
      "
      :component-name="el.componentName"
      :init-width="el.initWidth || 0"
      :index="i"
      :center-x="el.centerX"
      :center-y="el.centerY"
      :scale-factor="scaleFactor"
      @select="selectItem"
    />
    <LabelValue
      v-for="(el, i) in labelValueData"
      :ref="
        (ref) => {
          compInstance[el.id] = ref;
        }
      "
      :key="el.id"
      v-bind="el"
      :index="i"
      :scale-factor="scaleFactor"
    />
    <FlowPath
      v-for="(el, i) in flowPathData"
      :id="el.id"
      :key="el.id"
      :ref="
        (ref) => {
          compInstance[el.id] = ref;
        }
      "
      :init-width="(el.initWidth as number)"
      :init-height="(el.initHeight as number)"
      :center-x="el.centerX"
      :center-y="el.centerY"
      :scale-factor="scaleFactor"
      :type="el.flowPathType"
      :index="i"
    />
    <rect
      v-if="boxSelect"
      ref="boxSelectEl"
      v-bind="boxSelect"
      fill="#99ccff"
      fill-opacity="0.3"
      stroke="#0000dd"
      stroke-width="0.3"
    />
  </svg>
</template>
<script setup lang="ts">
import { CSSProperties, inject, nextTick, onBeforeUnmount, onMounted, ref, shallowRef, watch } from "vue";
import type { BoxSelect, GridLineExpose, MainSvgExpose, PreviewDragBounding, RootCommon } from "./types";
import { RootCommonKey } from "./types";
import { GridShares } from "/@/components/common";
import GridLine from "./GridLine.vue";
import PipePath from "/@/components/PipePath/PipePath.vue";
import Device from "/@/components/Device/Device.vue";
import LabelValue from "/@/components/LabelValue/LabelValue.vue";
import FlowPath from "/@/components/FlowPath/FlowPath.vue";
import type { PipePathType, CompCommon, Point } from "/@/components/types";
import { CompCommonKey } from "/@/components/types";
import ResizeObserver from "resize-observer-polyfill";
import { isMobile } from "/@/utils/common";
import { calc2pointDistance } from "/@/utils/draw";

defineProps<{
  boxSelect?: BoxSelect;
}>();

const {
  deviceSvgData,
  deviceImageData,
  pipePathData,
  labelValueData,
  compInstance,
  scaleFactor,
  svgWidth,
  svgHeight,
  flowPathData,
  previewScale,
  previewScaleChange,
  selectItem,
} = inject(RootCommonKey) as RootCommon;

const { initSvgInfo, isEditor } = inject(CompCommonKey) as CompCommon;
const svgEl = shallowRef<HTMLElement>();
const boxSelectEl = shallowRef<SVGRectElement | null>(null);
const gridLineIns = shallowRef<GridLineExpose | null>(null);
let resizeObserver: ResizeObserver | null;
let containerWidth = 0;
let containerHeight = 0;
onMounted(() => {
  if (svgEl.value) {
    initSvgInfo({
      svgEl: svgEl.value,
      boxSelectEl: boxSelectEl.value,
      gridLineIns: gridLineIns.value,
    });
    if (!isEditor.value) {
      position.value = "absolute";
      const containerEl = svgEl.value.parentNode as HTMLElement;
      resizeObserver = new ResizeObserver((entries: ResizeObserverEntry[]) => {
        const entry = entries[0];
        if (entry) {
          containerWidth = entry.contentRect.width;
          containerHeight = entry.contentRect.height;
          handleResize(entry.contentRect.width, entry.contentRect.height);
        }
      });
      resizeObserver.observe(containerEl);
      if (isMobile) {
        containerEl.addEventListener("touchstart", beforeDragStart);
        // document.addEventListener("touchmove", preventDefault, { passive: false });
      } else {
        containerEl.addEventListener("mousedown", beforeDragStart);
      }
    }
  }
});
onBeforeUnmount(() => {
  if (resizeObserver) {
    console.log("=======>ResizeObserver unmount");
    resizeObserver.disconnect();
    resizeObserver = null;
  }
  if (svgEl.value && !isEditor.value) {
    const containerEl = svgEl.value.parentNode as HTMLElement;
    if (isMobile) {
      containerEl.removeEventListener("touchstart", beforeDragStart);
      // document.removeEventListener("touchmove", preventDefault);
    } else {
      containerEl.removeEventListener("mousedown", beforeDragStart);
    }
  }
});

const position = ref<CSSProperties["position"]>("static");
const left = ref(0);
const top = ref(0);
//根据父容器尺寸 调整svg尺寸自适应
function handleResize(containerWidth, containerHeight) {
  let newWidth = containerHeight * svgWHFactor;
  if (newWidth > containerWidth) {
    svgHeight.value = containerWidth / svgWHFactor;
    svgWidth.value = containerWidth;
  } else {
    svgHeight.value = containerHeight;
    svgWidth.value = newWidth;
  }
  left.value = (containerWidth - svgWidth.value) / 2;
  top.value = (containerHeight - svgHeight.value) / 2;
}
//预览组态图时 内容旁边的留白尺寸
const previewSpace = 30;
let svgWHFactor = 1;
const viewPortWidth = ref(0);
const viewPortHeight = ref(0);
const myExpose: MainSvgExpose = {
  initViewBox() {
    if (isEditor.value) return;
    let minX: number | null = null;
    let minY: number | null = null;
    let maxX: number | null = null;
    let maxY: number | null = null;
    for (const i in compInstance) {
      const ins = compInstance[i];
      const box = ins.getBox();
      if (minX === null || box.x < minX) {
        minX = box.x;
      }
      if (minY === null || box.y < minY) {
        minY = box.y;
      }
      if (maxX === null || box.maxX > maxX) {
        maxX = box.maxX;
      }
      if (maxY === null || box.maxY > maxY) {
        maxY = box.maxY;
      }
    }
    if (!minX) minX = 0;
    if (!minY) minY = 0;
    if (!maxX) maxX = svgWidth.value;
    if (!maxY) maxY = svgHeight.value;
    let dx = 0;
    let dy = 0;
    if (minX > previewSpace || minY > previewSpace) {
      dx = minX > previewSpace ? -(minX - previewSpace) : 0;
      dy = minY > previewSpace ? -(minY - previewSpace) : 0;
      for (const i in compInstance) {
        compInstance[i].translate(dx, dy);
      }
    }
    maxX += dx;
    maxY += dy;
    if (maxX + previewSpace < svgWidth.value) {
      svgWidth.value = maxX + previewSpace;
    }
    if (maxY + previewSpace < svgHeight.value) {
      svgHeight.value = maxY + previewSpace;
    }
    svgWHFactor = svgWidth.value / svgHeight.value;
    viewPortWidth.value = svgWidth.value;
    viewPortHeight.value = svgHeight.value;
  },
};
defineExpose(myExpose);

function beforeDragStart(e) {
  dragStart(e);
  if (isMobile) {
    document.addEventListener("touchmove", dragMove, { passive: false });
    document.addEventListener("touchend", beforeDragEnd);
    document.addEventListener("touchcancel", dragCancel);
  } else {
    document.addEventListener("mousemove", dragMove);
    document.addEventListener("mouseup", beforeDragEnd);
  }
}
function beforeDragEnd() {
  dragEnd();
  if (isMobile) {
    document.removeEventListener("touchmove", dragMove);
    document.removeEventListener("touchend", beforeDragEnd);
    document.removeEventListener("touchcancel", dragCancel);
  } else {
    document.removeEventListener("mousemove", dragMove);
    document.removeEventListener("mouseup", beforeDragEnd);
  }
}
let startData:
  | ({
      x: number;
      y: number;
      left: number;
      top: number;
      bodyWidth: number;
    } & PreviewDragBounding)
  | null = null;
function handleEvent(e: MouseEvent | TouchEvent) {
  let result: {
    x: number;
    y: number;
    x1: number;
    y1: number;
    isDoubleFinger: boolean;
  };
  if (e instanceof MouseEvent) {
    result = {
      x: e.pageX,
      y: e.pageY,
      x1: 0,
      y1: 0,
      isDoubleFinger: false,
    };
  } else {
    const changedTouches = e.changedTouches;
    result = {
      x: changedTouches[0].pageX,
      y: changedTouches[0].pageY,
      x1: changedTouches[1] ? changedTouches[1].pageX : 0,
      y1: changedTouches[1] ? changedTouches[1].pageY : 0,
      isDoubleFinger: changedTouches.length > 1,
    };
  }
  return result;
}
function getBounding(): PreviewDragBounding {
  const curSvgWidth = svgWidth.value * previewScale.value;
  const curSvgHeight = svgHeight.value * previewScale.value;
  let minLeft: number, maxLeft: number, minTop: number, maxTop: number;
  if (curSvgWidth > containerWidth) {
    minLeft = containerWidth - curSvgWidth;
    maxLeft = 0;
  } else {
    minLeft = 0;
    maxLeft = containerWidth - curSvgWidth;
  }
  if (curSvgHeight > containerHeight) {
    minTop = containerHeight - curSvgHeight;
    maxTop = 0;
  } else {
    minTop = 0;
    maxTop = containerHeight - curSvgHeight;
  }
  return {
    minLeft,
    maxLeft,
    minTop,
    maxTop,
  };
}
function handleLeftTop(newLeft: number, newTop: number, bounding: PreviewDragBounding) {
  if (newLeft < bounding.minLeft) {
    newLeft = bounding.minLeft;
  }
  if (newLeft > bounding.maxLeft) {
    newLeft = bounding.maxLeft;
  }
  if (newTop < bounding.minTop) {
    newTop = bounding.minTop;
  }
  if (newTop > bounding.maxTop) {
    newTop = bounding.maxTop;
  }
  return {
    left: newLeft,
    top: newTop,
  };
}
//以某个基点缩放后，获取svg的left top值
function getLeftTopAfterOriginalScale(curScale: number) {
  if (startScaleData) {
    const { left, top, scale, originalPoint } = startScaleData;
    const x = (originalPoint.x / (scale * svgWidth.value)) * (curScale * svgWidth.value);
    const y = (originalPoint.y / (scale * svgHeight.value)) * (curScale * svgHeight.value);
    const newLeft = left - (x - originalPoint.x);
    const newTop = top - (y - originalPoint.y);
    return {
      left: newLeft,
      top: newTop,
    };
  }
  return null;
}
function dragLog(...arg) {
  if (window.Logger) {
    window.Logger.log.apply(window.Logger, arg);
  }
  // console.log.apply(console, arg);
}
function dragStart(e: MouseEvent | TouchEvent) {
  const info = handleEvent(e);
  const bounding = getBounding();
  startData = Object.assign(
    {
      x: info.x,
      y: info.y,
      left: left.value,
      top: top.value,
      bodyWidth: document.body.offsetWidth,
    },
    bounding
  );
}
function getScale(scale: number) {
  if (!scale) scale = 1;
  if (scale > 5) {
    scale = 5;
  } else if (scale < 1) {
    scale = 1;
  }
  return scale;
}
//缩放的基准点
let startScaleData: {
  distance: number;
  scale: number;
  left: number;
  top: number;
  originalPoint: Point;
} | null = null;
//用户点击放大缩小按钮时的缩放处理前 要先重置startScaleData
previewScaleChange.value = () => {
  startScaleData = null;
};
let lastLeft: number | null = null;
let lastTop: number | null = null;
function dragMove(e: MouseEvent | TouchEvent) {
  if (startData) {
    e.preventDefault();
    e.stopPropagation();
    const info = handleEvent(e);
    if (info.isDoubleFinger) {
      const distance = calc2pointDistance({ x: info.x, y: info.y }, { x: info.x1, y: info.y1 });
      if (startScaleData) {
        const d = distance - startScaleData.distance;
        let scale = startScaleData.scale + d / startData.bodyWidth;
        dragLog("doubleFinger dragMove");
        previewScale.value = getScale(scale);
      } else {
        dragLog("init doubleFinger");
        if (svgEl.value) {
          const rect = svgEl.value.getBoundingClientRect();
          const centerPoint = { x: (info.x + info.x1) / 2, y: (info.x + info.y1) / 2 };
          startScaleData = {
            distance,
            scale: previewScale.value,
            left: left.value,
            top: top.value,
            originalPoint: { x: centerPoint.x - rect.x, y: centerPoint.y - rect.y },
          };
        }
      }
      return;
    } else {
      //回到单指  更新startData
      if (startScaleData) {
        dragLog("doubleFinger back to singleFinger");
        const bounding = getBounding();
        startData = Object.assign(
          {
            x: info.x,
            y: info.y,
            left: left.value,
            top: top.value,
            bodyWidth: document.body.offsetWidth,
          },
          bounding
        );
        startScaleData = null;
        return;
      }
    }
    const dx = info.x - startData.x;
    const dy = info.y - startData.y;
    let newLeft = startData.left + dx;
    let newTop = startData.top + dy;
    const re = handleLeftTop(newLeft, newTop, startData);
    if (lastLeft !== null && lastTop !== null) {
      const lastDx = Math.abs(re.left - lastLeft);
      const lastDy = Math.abs(re.top - lastTop);
      //正常单指拖动 上一次的xy跟当次的xy相差在100以内
      // dragLog(lastDx.toFixed(2), lastDy.toFixed(2));
      if (lastDx > 150 || lastDy > 150) {
        dragLog(lastDx.toFixed(2), lastDy.toFixed(2), "last dxy is too big, ignore");
        return;
      }
    }
    left.value = re.left;
    top.value = re.top;
    lastLeft = re.left;
    lastTop = re.top;
    dragLog("singleFinger dragMove");
  }
}
function dragEnd() {
  dragLog("dragEnd");
  handlerEnd();
}
function dragCancel() {
  dragLog("dragCancel");
  handlerEnd();
}
function handlerEnd() {
  lastLeft = null;
  lastTop = null;
  startScaleData = null;
}
watch(previewScale, (v1, v2) => {
  let shouldReset = false;
  if (!startScaleData) {
    shouldReset = true;
    startScaleData = {
      left: left.value,
      top: top.value,
      originalPoint: {
        x: (svgWidth.value * v2) / 2,
        y: (svgHeight.value * v2) / 2,
      },
      scale: v2,
      distance: 0,
    };
  }
  const re = getLeftTopAfterOriginalScale(v1);
  if (re) {
    const bounding = getBounding();
    const handleRe = handleLeftTop(re.left, re.top, bounding);
    left.value = handleRe.left;
    top.value = handleRe.top;
  }
  if (shouldReset) {
    startScaleData = null;
  }
});
</script>
